package ar.edu.caece.compiler.error;

import static ar.edu.caece.compiler.grammar.AbstractTokenVisitorFactory.backToken;
import static ar.edu.caece.compiler.grammar.AbstractTokenVisitorFactory.currentSymbol;
import static ar.edu.caece.compiler.grammar.AbstractTokenVisitorFactory.nextSymbol;
import static ar.edu.caece.compiler.symbol.SymbolTable.EOF;
import static ar.edu.caece.compiler.symbol.TerminalSymbol.isIndentifier;
import static org.apache.commons.logging.LogFactory.getLog;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;

import ar.edu.caece.compiler.grammar.Recognizer;
import ar.edu.caece.compiler.grammar.TokenRecognizer;
import ar.edu.caece.compiler.model.Grammar;
import ar.edu.caece.compiler.model.Position;
import ar.edu.caece.compiler.symbol.NonTerminalSymbol;
import ar.edu.caece.compiler.symbol.Symbol;
import ar.edu.caece.compiler.symbol.SymbolTable;
import ar.edu.caece.compiler.symbol.TerminalSymbol;

public class ErrorManager {
  
  private Log logger = getLog(ErrorManager.class);
  
  private static ErrorManager instance;
  
  private Grammar grammar;

  private List<Error> errors;
  
  private final static String TOKEN_SUFIX = "Token";
  
  public static ErrorManager getInstance() {
    if(instance == null)
      instance = new ErrorManager();
    return instance;
  }
  
  private ErrorManager() {
    errors = new ArrayList<Error>();
  }
  
  public void manage(TokenRecognizer recognizer, String parentNonTerminalLex, ParsingException ex) throws ParsingException {
    try {
      
      TerminalSymbol received = ((TokenRecognizer)ex.getReceived()).getToken().getSymbol();
      Position position = recognizer.getToken().getPos();
      Error e = null;
      
      if(received.getSubtype().equals(SymbolTable.ERROR_STR))
        e = new LexicalError(received, position);
      else 
        e= create(received, parentNonTerminalLex, ex.getExpected(), position);

      errors.add(e);
      logger.debug(e);
      
    } catch (Exception e) {
    	getLog(this.getClass()).debug("Error managing error, recognizer=" + recognizer + ", parent Non-Terminal=" + parentNonTerminalLex, e);
    	throw ex;
    }
  }
  
  private SyntaxError create(TerminalSymbol received, String parentNonTerminalLex, Class<? extends Recognizer> expectedRecognizer, Position position) {
    Symbol expected = getExpected(expectedRecognizer);
    NonTerminalSymbol parentNonTerminal = (NonTerminalSymbol)grammar.getSymbol(parentNonTerminalLex);
    List<TerminalSymbol> posibles = grammar.getFollowing(parentNonTerminal);

    for(Symbol symbol = currentSymbol(); !symbol.equals(EOF); symbol = nextSymbol()) 

      if(posibles.contains(symbol) || (isIndentifier(symbol) && posibles.contains(grammar.getSymbol("id")))) {
        backToken();
        break;
      }

    return new SyntaxError(expected, received, position);    
  }
  
  private Symbol getExpected(Class<? extends Recognizer> recognizer) {
    String lexema = recognizer.getSimpleName();
    final int pos = lexema.indexOf(TOKEN_SUFIX);
        
    if(pos > 0) 
      lexema = lexema.substring(0, pos);
    
    return grammar.getSymbol(lexema.toLowerCase());
  }
  
  public void addSemanticError(String message, Position position) {
	  Error e = new Error(null,position,message+ " en " + position);
	  errors.add(e);
      logger.debug(e);
  }

  public void setGrammar(Grammar grammar) {
    this.grammar = grammar;
  }

  public List<Error> getErrors() {
    return errors;
  }
}
